home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Programming Languages Suite
/
ProgramD2.iso
/
Borland
/
Borland C++ For TASM
/
HEAP.PAK
/
MEM.ASM
< prev
next >
Wrap
Assembly Source File
|
1996-02-21
|
38KB
|
1,360 lines
page 75,132
%title "Object Oriented Memory Management System - TASM 4.0"
; Copyright (c) 1993 by Borland International
ifndef MDL
display "Error: This module requires that you provide a memory model"
display " definition on the command line. I.E. /dMDL=SMALL."
display "Also, /m must be given on the command line to enable multiple"
display " passes."
MDL equ <SMALL>
endif
ModuleVersion EQU "0.33"
COMMENT ~
This memory management system shows how to use TASM 3.0 objected oriented
extensions to create a heap management system. Some features of this heap
management system are:
- Extensive use of objects for the heap manager and also individual
blocks of memory
- Extensibility to allow changing the structure of heap blocks without
drastic changes to the heap manager
- Ability to mix many different types of blocks in the same heap,
for example, disk heap objects interspersed with regular memory
blocks.
- Extensive optimizations
The memory management system is implemented by several object classes.
All memory accesses are based on requesting a block of memory whose size
is given in paragraphs. Users of the memory system only need to store
segment offsets of the memory blocks that are returned. All memory blocks
can return the offset, where the user may begin storing information,
whenever needed at a later time.
memory_system is the master heap object. It is responsible for allocating
a block of memory to be managed as a heap. This block of
memory may be smaller than the maximum amount of available
memory, enabling the user of this system to setup multiple
heaps if desired.
memory_block is the object that has the methods for the operations of
individual blocks in the heap. It may be used for blocks
that are used, or unused. Although extensive checks
of IsFree are made in routines that are not allowed on used
blocks of memory, (for example splitting a block apart to
create a smaller block to satisfy an allocation request),
there are more specialized blocks objects available to
handle used blocks and the endblock at the end of the heap.
Consequently, the _DO_ISFREE_ equate may be disabled, and
routines within memory_block will rely on the method
overrides in the memory_usedblock class to filter out calls
that should not happen to memory_blocks. This alone results
in a 20% speed increase of the memory manager in the
set of testing benchmarks!
memory_usedblock is an object that handles the operations for blocks on the
heap that are in use. It is a descendant of the memory_block
object. It is not strictly necessary that blocks on the heap
be retyped as memory_usedblock, (by changing the VMT), but
it should allow faster operations since the usedblock
automatically traps out routines that it shouldn't be allowed
to do, thus eliminating the need to check IsFree.
memory_endblock is a specialized object that is placed at the end of the
heap. It is a descendant of the memory_usedblock object.
It should have no size, and is strictly a placemarker so
that all other blocks will have a next pointer other than
zero, so they will all be able to use segment arithmetic
to calculate their sizes.
Object Hierarchy:
MEMORY_SYSTEM
MEMORY_BLOCK
:
:
MEMORY_USEDBLOCK
:
:
MEMORY_ENDBLOCK
For compatibility with future memory_block object descendants, a LockBlock
call should be given for all memory blocks prior to usage, and an UnLockBlock
call should be given whenever the object is free to move about. When a
LockBlock call is given, DS:SI will be set on return to point to an actual
memory address that should be used for operating on the area of memory. The
UnLockBlock call must give this same memory address. UnlockBlock returns
a value in DS:SI that can be used for the next LockBlock call.
The ALLOC operation of the memory_system always locks it's block prior to
returning. Therefore it always returns a real memory pointer. When the memory
area has been initialized, it may be unlocked, and the return of the
UnLockBlock call may be stored for future accesses to the memory region.
This convention can allow implementing a special kind of memory block that
knows how to swap itself to offline storage when it is not in use.
In practice, this system may be implemented by having objects on the heap
that represent unlocked blocks of memory. When this block recieves a lock
call, it creates a region in memory to recieve the memory contents and then
it loads the actual memory contents from offline storage. A pointer to this
new block is returned. Later, when this new block is freed, the old lock
block is informed and the lock block may swap the memory area to disk.
Normal memory_blocks, and memory_usedblocks, do not process the LockBlock or
UnLockBlock calls in any way. So how do blocks that implement swapping come
to be included in the memory system? A process outside the memory_system,
a caller of memory_system, can implement this. The process calls the
memory_system to give it a block of memory. (This block of memory will
probably be the size of the swapping_block object.) This block of memory
can then be retyped by changing it's VMT. The new VMT will then point to
the method table for a different class of object that implements swapping
of it's memory region. (The outside process might make another call to
the swapping_block object that informs the swapping block how big of an
area it is managing.) As long as the memory_block can respond to normal
requests that the memory_system makes, (it will probably respond with the
methods of the memory_usedblock, while any special messages that it takes
are added to the end of the memory_usedblock VMT), the existance of the
block will not cause any problems. It can transparently handle LockBlock and
UnLockBlock calls by creating and destroying new blocks of memory within
the memory_system that it belongs to, because the outside process would
have passed it a handle to the memory_system, and also any handles or
information that it needs for accessing the offline virtual storage that
it swaps information in and out of.
Of course, it is possible that the memory_system itself might be able to
transparently create these new types of memory_blocks. If the memory_system
is passed a pointer to the init method for a new type of block, then
it would automatically deal with the new type of block. The init method
for memory_blocks requires the size of the block to be passed. Currently
this is set to be the same size as the block that the memory_system
selected.
~
jumps
page
locals @@
; Define the following to enable the optimization of the rover pointer
; to shorten time to find a free heap area.
_USE_ROVER_ =1
; Define the following to enable a freed block to be combined with
; any free block that might be immediately before it.
; Defining this equate causes a tremendous slow down in the deallocation
; process, since a scan for previous must be done for every free operation.
; This scan for previous has to walk the entire memory block chain up to
; the item that is being freed in order to find the previous block and see
; if it is free.
; Logic has been added instead to the alloc routine to combine adjacent
; free blocks that it finds while it is walking forward through the
; chain of memory blocks.
;_COMBINE_PREV_=1
; The PREV pointer for unused blocks is not always accurate, since
; scanprev is not done during dealloc. Do not define the following.
;_DO_SETPREV_ = 1
; Since used blocks have a different VMT pointer than unused blocks,
; routines that are not valid for used blocks, (e.g. setprev and setsize),
; can be trapped out from the default routine in the memory_block object.
; Then those memory block objects can assume that they were called because
; the block is free. If this equate is not defined, it is important that
; all used blocks, (and end blocks), are not left as regular memory_blocks,
; but are instead retyped as what they are by changing the VMT.
;__DO_ISFREE_ = 1
.model small,pascal
.code
; Int 21 DOS calls
DOSINT = 21H
DOSGETMEMBLOCK = 48H
DOSRELEASEBLOCK = 49H
DOSRESIZEBLOCK = 4AH
DOSTERMINATE = 4CH
; Include the utility macros that ease dealing with
; the Virtual Method Tables.
include vmtutil.inc
IsZero macro reg
or reg,reg
endm
; Include the names of routines that handle display of numbers.
include display.inc
; Cause the VMT tables to be defined as a part of this module.
_MAKE_MEMVMT_ = 1
include mem.inc
PAGE
;************************************ Routines for MEMORY_BLOCK
memory_block_reserved_size proc
mov bx,offset @memory_block_used_start
ret
memory_block_reserved_size endp
; AX has size of block to allocate into the chain.
memory_block_initget proc pascal previousblock:word,nextblock:word
ret
memory_block_initget endp
memory_block_init proc pascal uses ax,\
previousblock:word,nextblock:word,\
bsize:word
; Set the VMT PTR within this block!
mov [si.@Mptr_memory_block],offset @TableAddr_memory_block
;if @CodeSize eq 1
; mov word ptr [si.@Mptr_memory_block+2],seg @TableAddr_memory_block
;endif
ifdef _DO_SETPREV_
mov ax,[previousbLock]
mov word ptr [si.prev],ax
endif
mov ax,FREE_BLOCK
mov word ptr [si.next],ax
mov ax,[nextblock]
mov word ptr [si.next2],ax
mov ax,[bsize]
mov [si.blksize],ax
xor ax,ax
; Zero out the unused high words
mov word ptr [si.next+2],ax
mov word ptr [si.next2+2],ax
ifdef _DO_SETPREV_
mov word ptr [si.prev+2],ax
endif
ret
memory_block_init endp
memory_block_deinit proc
ret
memory_block_deinit endp
; AX has the previous block segment
; If combine is done, DS points to the previous segment.
memory_block_combine proc
ifdef _DO_ISFREE_
call [si] method memory_block:IsFree
jnz @@done
endif
push cx
mov cx,ax ; Save AX, so we can set it's block to point to the
; next of the DS block.
call [si] method memory_block:GetNext
mov ds,cx
call [si] method memory_block:SetNext
; Also, need to update size of previous block
; AX still contains seg of block after one being freed.
sub ax,cx
dec ax
call [si] method memory_block:SetSize
pop cx
@@done:
ret
memory_block_combine endp
mb_show proc uses ds es dx ax
.data
@@used db "Used$"
@@free db "Free" ;,"$"
@@next2 db " Next2:$"
@@prev db " Prev:$"
@@blksize db " Blksize:$"
.code
call Show_Bracket
call [si] method memory_block:IsFree
; Put our pointer into ES:SI
push ds
pop es
mov ax,@data
mov ds,ax
; Print a USED or FREE string
mov ah,DOSPRINTSTRING
jz @@block_is_free
; We should never get here if memory_usedblocks are used.
mov dx,offset @@used
int DOSINT
jmp @@done
@@block_is_free:
mov dx,offset @@free
@@show_msg:
int DOSINT
; mov ah,DOSPRINTSTRING
; mov dx,offset @@next2
; int DOSINT
mov ax,word ptr es:[si.next2]
call ShowHexWord
ifdef _DO_SETPREV_
mov ah,DOSPRINTSTRING
mov dx,offset @@prev
int DOSINT
mov ax,word ptr es:[si.prev]
call ShowHexWord
endif
mov ah,DOSPRINTSTRING
mov dx,offset @@blksize
int DOSINT
mov ax,word ptr es:[si.blksize]
call ShowHexWord
@@done:
call Show_Endbracket
ret
mb_show endp
mb_findprev proc
local findprevfor:word
mov [findprevfor],ax
@@check:
LoadVMTSeg es,ax
call [si] method memory_block:GetNext
cmp ax,[findprevfor]
je @@previousfound
IsZero ax
je @@noprevious
; Load the segment of the next block, and go check it out
mov ds,ax
jmp @@check
@@previousfound:
mov ax,ds ; Store the segment of the current block in AX.
jmp @@done
@@noprevious:
xor ax,ax ;Zero out AX to indicate no previous.
@@done:
ret
mb_findprev endp
mb_getnext proc
ifdef _DO_ISFREE_
mov ax,word ptr [si.next]
cmp ax,FREE_BLOCK
jne @@done
endif
mov ax,word ptr [si.next2]
@@done:
ret
mb_getnext endp
mb_isfree proc
ifdef _DO_ISFREE_
cmp word ptr [si.next],FREE_BLOCK
else
; All we need to do is set the Z flag!
cmp ax,ax
endif
ret
mb_isfree endp
mb_setprev proc
ifdef _DO_SETPREV_
ifdef _DO_ISFREE_
LoadVMTSeg es,bx
call [si] method memory_block:IsFree
jnz @@1
endif
mov word ptr [si.prev],ax
@@1:
endif
ret
mb_setprev endp
mb_setsize proc
ifdef _DO_ISFREE_
LoadVMTSeg es,bx
call [si] method memory_block:IsFree
jnz @@1 ; short @memory_block_setsize_done
endif
mov word ptr [si.blksize],ax
ifdef _DO_ISFREE_
@@1:
endif
ret
mb_setsize endp
mb_setnext proc
ifdef _DO_ISFREE_
LoadVMTSeg es,bx
call [si] method memory_block:IsFree
jnz short @@notfree
endif
; If we are here, the block is free.
mov word ptr [si.next2],ax
ifdef _DO_ISFREE_
jmp short @@done
@@notfree:
mov word ptr [si.next],ax
@@done:
endif
ret
mb_setnext endp
mb_scan proc
; LoadVMTSeg es,bx
@@check:
call [si] method memory_block:IsFree
; cmp word ptr [si.next],FREE_BLOCK ; See if this block is free
jz @@found
; It's not free. So go to the next block.
call [si] method memory_block:GetNext
IsZero ax ; Check for the last block in the chain (next=0?)
jz @@done
mov ds,ax
jmp @@check
@@found:
; Load the size of the current block into AX
; Size in paragraphs is (seg of next block) - (seg of this block)
mov ax,ds
neg ax
add ax,word ptr [si.next2]
dec ax ; Adjust for block bookkeeping information
; mov ax,[si.blksize] ; This should produce the same
; value for free blocks.
@@done:
ret
mb_scan endp
mb_rawblocksize proc
; LoadVMTSeg es,bx
call [si] method memory_block:GetNext
mov bx,ds
sub ax,bx
ret
mb_rawblocksize endp
mb_breakblock proc uses ds si
local desiredsize:word,currentnext:word,newnext:word,currblock:word,\
newblocksize:word
mov [desiredsize],ax
mov ax,ds
mov [currblock],ax
mov [newnext],ax ; We'll use newnext as the address to return in
; AX.
; This routine is only valid for free memory blocks
; LoadVMTSeg es,bx
ifdef _DO_ISFREE_
call [si] method memory_block:IsFree
jnz @@done
endif
call [si] method memory_block:RawBlockSize
mov bx,ax
sub bx,[desiredsize]
dec bx ; Represent bookkeeping information of current block
dec bx ; For bookkeeping information of the new block
mov [newblocksize],bx
cmp bx,2 ; If less than four paragraphs left over, don't
; do the breakdown.
jbe @@done
; Get the current next block
call [si] method memory_block:GetNext
mov [currentnext],ax
; Calculate the segment of the new block
mov ax,ds
add ax,[desiredsize]
inc ax ; For the size of our bookkeeping info
mov [newnext],ax
; Set our next pointer to new block
call [si] method memory_block:SetNext
; Set our size
mov ax,[desiredsize]
call [si] method memory_block:SetSize
; Set the new block to point to its neighbors
mov ds,[newnext]
call [si] method memory_block:init pascal,[currblock],[currentnext],[newblocksize]
ifdef _DO_SETPREV_
; Set the old next block to point to the new block created just
; before it.
mov ds,[currentnext]
call [si] method memory_block:setprev
endif
@@done:
mov ax,[newnext]
ret
mb_breakblock endp
memory_block_markfree proc
ifdef _DO_ISFREE_
local previous:word
mov [previous],ax
call [si] method memory_block:IsFree
jz @@block_is_free_skip
; Get the pointer to the next block
call [si] method memory_block:GetNext
; Calculate the size of this block
push ax
mov bx,ax
mov ax,ds
sub bx,ax
dec bx ; For the bookkeeping area
pop ax
call [si] method memory_block:init pascal,[previous],ax,bx
@@block_is_free_skip:
endif
ret
memory_block_markfree endp
memory_block_markused proc
ifdef _DO_ISFREE_
call [si] method memory_block:IsFree
jnz @@done
endif
mov ax,word ptr [si.next2]
mov word ptr [si.next],ax
mov ax,word ptr [si.next2+2]
mov word ptr [si.next+2],ax
; Change the block type to be a used block.
; Do this by changing the VMT!
mov [si.@Mptr_memory_block],offset @TableAddr_memory_usedblock
;if @CodeSize eq 1
; mov word ptr [si.@Mptr_memory_block+2],seg @TableAddr_memory_usedblock
;endif
@@done:
ret
memory_block_markused endp
memory_block_lock proc
ret
memory_block_lock endp
memory_block_unlock proc
ret
memory_block_unlock endp
memory_block_allocfail proc
ret
memory_block_allocfail endp
page
;***** Routines for MEMORY_USEDBLOCK
memory_usedblock_markfree proc
local previous:word
mov [previous],ax
ifdef _DO_ISFREE_
call [si] method memory_block:IsFree
jz @@block_is_free_skip
endif
; Get the pointer to the next block
call [si] method memory_block:GetNext
; Calculate the size of this block
push ax
mov bx,ax
mov ax,ds
sub bx,ax
dec bx ; For the bookkeeping area
pop ax
call [si] method memory_block:init pascal,[previous],ax,bx
@@block_is_free_skip:
ret
memory_usedblock_markfree endp
memory_usedblock_combine proc
ret ;Can't do a combine with a used block. So ignore it!
memory_usedblock_combine endp
memory_usedblock_setnext proc
mov word ptr [si.next],ax
ret
memory_usedblock_setnext endp
memory_usedblock_invalid proc
ret
memory_usedblock_invalid endp
memory_usedblock_isfree proc uses ax
mov ax,1 ; Set the Z flag to zero!
IsZero ax
ret
memory_usedblock_isfree endp
memory_usedblock_scan proc
; This block is not free, so quickly pass the call
; along to the next object in this chain!
mov ax,word ptr [si.next]
mov ds,ax
IsZero ax ; Check for zero, meaning end of the line!
jz @@done
; Use a jump, instead of
; call [si] method memory_usedblock:ScanFree
; so that we eliminate overflowing the stack during this routine.
MOV BX,[([ SI ]).@Mptr_memory_block]
;if @CodeSize eq 1
; MOV ES,word ptr [([ SI ]).@Mptr_memory_block+2]
;endif
jmp es:[(@Table_memory_usedblock PTR BX).ScanFree]
@@done:
ret
memory_usedblock_scan endp
memory_usedblock_getnext proc
mov ax,word ptr [si.next]
ret
memory_usedblock_getnext endp
memory_usedblock_init proc
; Set the VMT PTR within this block!
mov [si.@Mptr_memory_block],offset @TableAddr_memory_usedblock
ret
memory_usedblock_init endp
memory_usedblock_show proc uses ds es dx ax
.data
@@type db "USED$"
@@next db " Next:$"
.code
call Show_Bracket
push ds
pop es
mov ax,@data
mov ds,ax
mov dx,offset @@type
mov ah,DOSPRINTSTRING
int DOSINT
mov ah,DOSPRINTSTRING
mov dx,offset @@next
int DOSINT
mov ax,word ptr es:[si.next]
call ShowHexWord
call Show_Endbracket
ret
memory_usedblock_show endp
memory_usedblock_lock proc
ret
memory_usedblock_lock endp
memory_usedblock_unlock proc
ret
memory_usedblock_unlock endp
page
;***** Routines for MEMORY_ENDBLOCK
memory_endblock_ignore proc
ret
memory_endblock_ignore endp
memory_endblock_getnext proc
xor ax,ax
ret
memory_endblock_getnext endp
memory_endblock_init proc
; Set the VMT PTR within this block!
mov [si.@Mptr_memory_block],offset @TableAddr_memory_endblock
;if @CodeSize eq 1
; mov word ptr [si.@Mptr_memory_block],seg @TableAddr_memory_endblock
;endif
xor ax,ax
mov word ptr [si.next],ax
mov word ptr [si.next+2],ax
ret
memory_endblock_init endp
memory_endblock_show proc uses ds dx ax
.data
@@end db "END$"
.code
call Show_Bracket
mov ax,@data
mov ds,ax
mov dx,offset @@end
mov ah,DOSPRINTSTRING
int DOSINT
call Show_Endbracket
ret
memory_endblock_show endp
page
;***** Routines for MEMORY_SYSTEM
memory_system_init proc uses es ds si bx
local endblockseg:word
; Fill in the VMT for the memory system
mov [si.@Mptr_memory_system],offset @TableAddr_memory_system
; Get a block of memory.
mov bx,ax ; Get the desired size, unless it is zero,
; then get the largest possible block.
or bx,bx
jnz @Memory_system_init_Alloc
dec bx ;mov bx,0FFFFh ; Go for largest possible
@Memory_system_init_Alloc:
mov [si.blocksize],bx
mov ah,DOSGETMEMBLOCK
int DOSINT
; Need to handle fail here! (Carryflag set, and BX with size of
; largest block available. )
jnc AllocSuccess
; Check the high bit of blocksize to see
; if we can get a different size.
; Note that if we come here a second time, it is because DOS
; said a certain size was available, and then it wouldn't give
; it when requested. The high bit wont be set on the second call.
TESTFLAG [si.blocksize],8000h
jz AllocFailed
; BX has size of largest block available. Go get it if it is NZ.
or bx,bx
jnz @Memory_system_init_Alloc ; Jump back and get it if there
; is some available.
AllocFailed:
mov [si.blocksize],0
jmp @Memory_system_init_done
AllocSuccess:
; Success: Carryflag clear, and AX initial segment of allocated block
mov [si.root],ax ; Segment of start of block still in AX!
mov [si.rover],ax
; Load the size of the block into BX
mov bx,[si.blocksize]
dec bx ; For bookkeeping
dec bx ; For the endblock
; Calculate in CX the segment of the endblock
mov cx,bx
inc cx ; For bookkeeping for first block
add cx,ax ; To get segment
mov [endblockseg],cx
mov [si.last],cx
; Save the address of the memory system, so we
; can call constructor for the memory block.
push ds
push si
; Load the pointer to the block into DS:SI
mov ds,ax
xor si,si
xor ax,ax ; For pushing zeros for prev & next blocks
; ; Need to set the VMT for the memory block before calling init!
; ; This is only needed if INIT routine is in the VMT!
; mov [si.@Mptr_memory_block],offset @TableAddr_memory_block
LoadVMTSeg ES
call [si] method memory_block:init pascal, ax,cx,bx
; Now make an endblock
mov ds,[endblockseg]
call [si] method memory_endblock:init
; Restore pointre to the memory system
pop si
pop ds
@Memory_system_init_done:
mov [si.usedspace],0
mov ax,[si.blocksize]
dec ax ; Bookkeeping for the first block
dec ax ; Bookkeeping for the end block
mov [si.freespace],ax
dec [si.freespace]
ret
memory_system_init endp
memory_system_deinit proc
; Return the memory to DOS.
mov es,[si.root]
mov ah,DOSRELEASEBLOCK
int DOSINT
ret
memory_system_deinit endp
memory_system_resetrover proc
mov bx,[si.root]
mov [si.rover],root
ret
memory_system_resetrover endp
.data
mss_initial db "Memory System Show:",CR,LF
db "System at:$"
mss_root db " Root block at:$"
mss_last db " Last block at:$"
mss_rover db " Rover block at:$"
mss_free db " Freespace:$"
mss_used db " Usedspace:$"
mss_blocks db "Blocks at:$"
mss_no_blks db "No blocks in heap.$"
.code
memory_system_show proc uses ax dx es ds
; Move pointer to the memory system from DS:SI, to ES:SI
push ds
pop es
; Now load pointer to our messages
mov dx,@data
mov ds,dx
; Show the location of the memory system
mov dx,offset mss_initial
mov ah,DOSPRINTSTRING
int DOSINT
mov ax,es
call ShowHexWord
mov dl,':'
mov al,DOSPRINTCHAR
int DOSINT
mov ax,si
call ShowHexWord
call CRLF
; Show the internal variables of the memory system
mov dx,offset mss_root
mov ah,DOSPRINTSTRING
int DOSINT
mov ax,es:[si.root]
call ShowHexWord
call CRLF
mov dx,offset mss_last
mov ah,DOSPRINTSTRING
int DOSINT
mov ax,es:[si.last]
call ShowHexWord
call CRLF
mov dx,offset mss_rover
mov ah,DOSPRINTSTRING
int DOSINT
mov ax,es:[si.rover]
call ShowHexWord
call CRLF
mov dx,offset mss_free
mov ah,DOSPRINTSTRING
int DOSINT
mov ax,es:[si.freespace]
call ShowHexWord
call CRLF
mov dx,offset mss_used
mov ah,DOSPRINTSTRING
int DOSINT
mov ax,es:[si.usedspace]
call ShowHexWord
call CRLF
; Save pointer to the memory system
push es
push si
mov ax,es:[si.root]
xor si,si
; Check for empty chain
cmp ax,0
jz mss_no_blocks
push ax
; Now we need to walk the chain
mov dx,offset mss_blocks
mov ah,DOSPRINTSTRING
int DOSINT
pop ax
mss_show_block:
mov ds,ax
call ShowHexWord
LoadVMTSeg ES,AX
call [si] method memory_block:show
call [si] method memory_block:GetNext
; See if it is the last block
IsZero ax
jz mss_show_block_done
push ax
mov ah,DOSPRINTCHAR
mov dl,','
int DOSINT
pop ax
jmp mss_show_block
; Branch here if this heap system is still empty
mss_no_blocks:
mov dx,offset mss_no_blks
mov ah,DOSPRINTSTRING
int DOSINT
mss_show_block_done:
pop si
pop es
call CRLF
ret
memory_system_show endp
memory_system_freeall proc
mov ax,[si.root]
@@free_another:
; If it is the end block, dont try to free it.
cmp ax,[si.last]
jz @@done
push ax
call [si] method memory_system:free
pop ax
push ds
push si
mov ds,ax
xor si,si
call [si] method memory_block:GetNext
IsZero ax
pop si
pop ds
jnz @@free_another
@@done:
ret
memory_system_freeall endp
memory_system_findprev proc uses ds si
; local systemseg:word,systemofs:word,findprevfor:word
; mov [systemseg],ds
; mov [systemofs],si
; mov [findprevfor],ax
; Check if this block is the root block
cmp ax,[si.root]
jz @memory_system_findprev_atroot
; Make pointer to first block in memory system
mov ds,[si.root]
xor si,si
call [si] method memory_block:MatchNext
cmp ax,0
jne @memory_system_findprev_done
; The block wasn't found!
@memory_system_findprev_atroot:
@memory_system_findprev_done:
; mov ds,[systemseg]
; mov si,[systemofs]
ret
memory_system_findprev endp
memory_system_blockofs proc uses ds si
cmp ax,0
jne @memory_system_blockofs_getit
mov ax,[si.root] ; Get the root and use it's offset since
; no segment was passed in
@memory_system_blockofs_getit:
mov ds,ax
xor si,si
call [si] method memory_block:memstart
ret
memory_system_blockofs endp
memory_system_alloc proc uses ds si
local allocsize:word,\
allocblock:word,\
largest:word,\ ; The size of the largest block encountered
largestseg:word,\
newfree:word,\
rootscan:word,\
memsysaddr:dword
mov [largest],0
mov [rootscan],0
mov word ptr [memsysaddr],si
mov word ptr [memsysaddr+2],ds
mov [allocsize],ax
ifdef _USE_ROVER_
mov ax,[si.rover]
else
mov ax,[si.root]
endif
mov ds,ax
@@scan:
xor si,si
call [si] method memory_block:ScanFree
; Check if there is enough room in this free block
cmp ax,[largest]
jbe @@scan_2
mov [largest],ax
mov [largestseg],ds
@@scan_2:
cmp ax,[allocsize]
ja @@found
mov ax,ds
IsZero ax
je @@no_more_blocks
call [si] method memory_block:GetNext
IsZero ax
je @@no_more_blocks
; The following block may also be free. Try to combine them.
; Swap DS and AX
mov bx,ds ; DS has the previous block!
mov ds,ax
mov ax,bx ; Make sure AX has the previous block
call [si] method memory_block:Combine
jmp @@scan
; No more blocks to scan!
@@no_more_blocks:
ifdef _USE_ROVER_
; Scan again from the root, because a big enough
; block could be prior to rover.
cmp [rootscan],0
jne @@scan_root
inc [rootscan]
lds si,dword ptr [memsysaddr]
mov ax,[si.root]
; Check to see that rover wasn't already pointing at the root
cmp ax,[si.rover]
je @@scan_root
mov ds,ax
jmp @@scan
@@scan_root:
endif
; Set rover to point to this new largest memory block,
; because combines might have invalidated the rover pointer.
lds si,dword ptr [memsysaddr]
mov ax,[largestseg]
mov [si.rover],ax
xor ax,ax
mov bx,[largest]
jmp @@done
@@found:
mov [allocblock],ds
; See how much is leftover in the block after the memory needed
; for this alloc call is taken out
mov ax,[allocsize]
call [si] method memory_block:BreakBlock
ifdef _USE_ROVER_
mov [newfree],ax
endif
; Mark the block as used!
call [si] method memory_block:MarkUsed
call [si] method memory_block:LockBlock
call [si] method memory_block:memstart
push bx
; If enough area was leftover, the extra area has been
; broken off to make a separate block.
lds si,dword ptr [memsysaddr]
ifdef _USE_ROVER_
; Update rover
mov ax,[newfree]
mov [si.rover],ax
endif
mov ax,[allocblock]
pop bx
; call [si] method memory_block:memstart
@@done:
ret
memory_system_alloc endp
memory_system_free proc uses ds si bx
local prevblock:word,\ ; Address of the block previous to this one
freeblock:word,\ ; Address of block that is being freed
systemaddr:dword
mov word ptr [systemaddr],si
mov word ptr [systemaddr+2],ds
mov [freeblock],ax
cmp ax,0
jz @@done
; Check the block to make sure that it is not already free
; Note that an invalid block address here will likely hang the
; memory manager, since the VMT will not be valid.
mov ds,ax
xor si,si
ifdef _DO_ISFREE_
call [si] method memory_block:IsFree
jz @@done
endif
call [si] method memory_block:UnLockBlock
lds si,dword ptr [systemaddr]
mov ax,[freeblock]
cmp ax,[si.root]
je @@free_root ; Special routine to free the root block
ifdef _COMBINE_PREVIOUS_
; Prior to freeing the block, get the address of
; the previous block. If no previous block is found,
; then this is a bogus FREE request and will be ignored.
call [si] method memory_system:findprev
IsZero ax ; Check for zero.
jz @@done
; There is a previous block
mov [prevblock],ax
endif
; Mark the current block as free.
mov ds,[freeblock]
xor si,si
call [si] method memory_block:MarkFree
ifdef _COMBINE_PREVIOUS_
mov ds,[prevblock]
xor si,si
call [si] method memory_block:IsFree
mov ds,[freeblock]
jnz @@try_combine_next
else
jmp @@try_combine_next
endif
@@combine_previous:
; The previous block is free, therefore it can be combined with
; this one. Set the previous block to point to our next block.
call [si] method memory_block:GetNext
mov ds,[prevblock]
call [si] method memory_block:SetNext
; Also, need to update size of previous block
; AX still contains seg of block after one being freed.
mov bx,ds
sub ax,bx
dec ax
call [si] method memory_block:SetSize
mov [freeblock],ds
jmp @@try_combine_next
@@free_root:
; At this point we only have to worry is if the block following
; a free block so it can be combined with this one.
; AX is the block to free.
mov ds,ax
xor si,si
xor ax,ax ; Because there is not previous block
call [si] method memory_block:MarkFree
@@try_combine_next:
call [si] method memory_block:GetNext
mov ds,ax
call [si] method memory_block:IsFree
jnz @@No_more_combine
mov ax,[freeblock]
mov [prevblock],ax
jmp @@combine_previous
@@No_more_combine:
ifdef _USE_ROVER_
; Just in case the rover might have pointed to a block that was
; combined with another, set rover to the start of the new
; free block that was created.
lds si,dword ptr [systemaddr]
mov bx,[freeblock]
mov [si.rover],bx
endif
@@done:
ret
memory_system_free endp
.data
LastSeg dw seg zzlastseg
.code
shrink_memory proc
; Have to give up extra memory that we don't need
; so the memory system objects can get blocks of memory.
mov bx,[LastSeg]
mov ax,es
sub bx,ax ; Size of program as a number of segments is in BX
mov ah,DOSRESIZEBLOCK
int DOSINT
ret
shrink_memory endp
zzlastseg segment
zzlastseg ends
end